-- pe.vhd
-- by Brittle 2009

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.std_logic_misc.ALL;
USE ieee.std_logic_unsigned.ALL;
-- synthesis translate_off
Library XilinxCoreLib;
use ieee.std_logic_textio.all;
library std;
use std.textio.all;
-- synthesis translate_on

ENTITY pe IS
  GENERIC (
    cores : integer := 1);
  PORT (
  debug : out std_logic_vector(31 downto 0);
  -- global interface
  clk   : in  std_logic;
  rst   : in  std_logic;
  en    : in  std_logic;
  -- parameter interface
  n     : in  std_logic_vector(31 downto 0);
  i_ini : in  std_logic_vector(31 downto 0);
  i_end : in  std_logic_vector(31 downto 0);
  eps   : in  std_logic_vector(31 downto 0);
  -- ram interface
  rdy   : in  std_logic;
  req   : out std_logic;
  vld   : in  std_logic;
  addr  : out std_logic_vector(31 downto 0);
  p_x   : in  std_logic_vector(31 downto 0);
  p_y   : in  std_logic_vector(31 downto 0);
  p_z   : in  std_logic_vector(31 downto 0);
  p_m   : in  std_logic_vector(31 downto 0);
  -- control interface
  start : in  std_logic;
  done  : out std_logic;
  i_cnt : out std_logic_vector(31 downto 0);
  fifo_r: in  std_logic;
  a_x   : out std_logic_vector(31 downto 0);
  a_y   : out std_logic_vector(31 downto 0);
  a_z   : out std_logic_vector(31 downto 0));
END pe;

ARCHITECTURE structural of pe IS

  COMPONENT nbody_dp IS
    PORT (
      clk  : in  std_logic;
      sclr : in  std_logic;
      ce   : in  std_logic;
      accs : in  std_logic_vector(2 downto 0);
      accr : in  std_logic;
      pi_x : in  std_logic_vector(31 downto 0);
      pi_y : in  std_logic_vector(31 downto 0);
      pi_z : in  std_logic_vector(31 downto 0);
      pj_x : in  std_logic_vector(31 downto 0);
      pj_y : in  std_logic_vector(31 downto 0);
      pj_z : in  std_logic_vector(31 downto 0);
      pj_m : in  std_logic_vector(31 downto 0);
      eps  : in  std_logic_vector(31 downto 0);
      o_x  : out std_logic_vector(31 downto 0);
      o_y  : out std_logic_vector(31 downto 0);
      o_z  : out std_logic_vector(31 downto 0));
  END COMPONENT;

  COMPONENT fifo_32_32 IS
    port (
      clk           : IN  std_logic;
      din           : IN  std_logic_VECTOR(31 downto 0);
      rd_en         : IN  std_logic;
      rst           : IN  std_logic;
      wr_en         : IN  std_logic;
      dout          : OUT std_logic_VECTOR(31 downto 0);
      empty         : OUT std_logic;
      full          : OUT std_logic);
  END COMPONENT;

  -- drain all internal values of the full pipeline
  constant DRAIN_LENGTH : std_logic_vector(7 downto 0) := X"83"; -- 132-1
  -- output all internal values of the accumulator
  constant ACC_LENGTH   : std_logic_vector(7 downto 0) := X"BB"; -- (132-1) + x

  type state_type is (FSM_IDLE, FSM_SETUP,
                      FSM_FEED, FSM_DRAIN,
                      FSM_ACCUMULATE, FSM_FINISH);
  signal fsm_state : state_type;
  signal ctrl_vld  : std_logic;
  signal ctrl_pipe : std_logic_vector(131 downto 0);

  signal fsm_cnt   : std_logic_vector(7 downto 0);
  signal pj_cnt    : std_logic_vector(31 downto 0);
  signal i         : std_logic_vector(31 downto 0);
  signal j         : std_logic_vector(31 downto 0);
  signal i_req_raw : std_logic;
  signal j_req_raw : std_logic;
  signal i_req     : std_logic;
  signal j_req     : std_logic;
  signal conflict  : std_logic;
  signal acc_cnt   : std_logic_vector(3 downto 0);
  signal acc_run   : std_logic_vector(2 downto 0);

  signal pi_x      : std_logic_vector(31 downto 0);
  signal pi_y      : std_logic_vector(31 downto 0);
  signal pi_z      : std_logic_vector(31 downto 0);
  signal pj_x      : std_logic_vector(31 downto 0);
  signal pj_y      : std_logic_vector(31 downto 0);
  signal pj_z      : std_logic_vector(31 downto 0);
  signal pj_m      : std_logic_vector(31 downto 0);
  signal o_x       : std_logic_vector(31 downto 0);
  signal o_y       : std_logic_vector(31 downto 0);
  signal o_z       : std_logic_vector(31 downto 0);

  signal fifo_w    : std_logic;
  signal full      : std_logic;
  signal empty     : std_logic;

  signal accs      : std_logic_vector(2 downto 0);
  signal accr      : std_logic;
  signal sclr      : std_logic;
  signal ce        : std_logic;

BEGIN
  ---------- Host interface ----------
  -- output data FIFO
  fifo_w <= not full when fsm_state = FSM_FINISH else '0';
  U_xfifo :  fifo_32_32
    port map (
      clk           => clk    ,
      din           => o_x    ,
      rd_en         => fifo_r ,
      rst           => rst    ,
      wr_en         => fifo_w ,
      dout          => a_x    ,
      empty         => empty  ,
      full          => full    ); 
  U_yfifo :  fifo_32_32
    port map (
      clk           => clk    ,
      din           => o_y    ,
      rd_en         => fifo_r ,
      rst           => rst    ,
      wr_en         => fifo_w ,
      dout          => a_y    ,
      empty         => open   ,
      full          => open   ); 
  U_zfifo :  fifo_32_32
    port map (
      clk           => clk    ,
      din           => o_z    ,
      rd_en         => fifo_r ,
      rst           => rst    ,
      wr_en         => fifo_w ,
      dout          => a_z    ,
      empty         => open   ,
      full          => open   ); 

  -- generate the DONE signal after finish all P[i]
  process (clk)
  begin
    if rising_edge(clk) then
      if rst = '1' or start = '1' then
        done <= '0';
      elsif fsm_state = FSM_FINISH and i >= i_end then
        done <= '1';
      end if;
    end if;
  end process;

  ---------- core datapath ----------
--  ce <= not rst;
--  sclr <= '1' when fsm_state = FSM_IDLE else '0';
  ce <= '1';
  sclr <= '0';
  U0: nbody_dp
    port map (
      clk   => clk  ,
      sclr  => sclr ,
      ce    => ce   ,
      accs  => accs ,
      accr  => accr ,
      pi_x  => pi_x ,
      pi_y  => pi_y ,
      pi_z  => pi_z ,
      pj_x  => pj_x ,
      pj_y  => pj_y ,
      pj_z  => pj_z ,
      pj_m  => pj_m ,
      eps   => eps  ,
      o_x   => o_x  ,
      o_y   => o_y  ,
      o_z   => o_z  );

  ---------- memory interface ----------
  addr <= i when i_req = '1' else j;
  i_req <= i_req_raw and rdy; -- no command entry if mem_if is not ready
  j_req <= j_req_raw and rdy; -- no command entry if mem_if is not ready
  req <= i_req or j_req;
 
  ---------- control logic ----------
  -- 'i_cnt' is for host program to read reflecting the index of the
  -- newest data in the FIFO (with FWFT feature).
  -- We cannot use the 'i' index directly since there is a delay (at
  -- least 2 mclk) between the data is written to FIFO and that data is
  -- ready at the FIFO output port (thus the change of 'empty' signal). So
  -- we use the 'empty' signal to update 'i_cnt' to ensure that FIFO output
  -- is valid when host detects index changes.
  process (clk)
  begin
    if rising_edge(clk) then
      if start = '1' then
        i_cnt <= (others => '0');
      elsif empty = '0' then
        i_cnt <= i;
      end if;
    end if;
  end process;

  -- i counter : counts the processed points, P[i]
  -- updated when the result is written to FIFO
  process (clk) 
  begin
    if rising_edge(clk) then
      if start = '1' then
        i <= i_ini;
      elsif fifo_w = '1' then
        i <= i + cores;
      end if;
    end if;
  end process;

  -- store the current p[i] location vector after read from memory
  U_Pi: process (clk) 
  begin
    if rising_edge(clk) then
      if rst = '1' then
        pi_x <= (others => '0');
        pi_y <= (others => '0');
        pi_z <= (others => '0');
      elsif fsm_state = FSM_SETUP and vld = '1' then
        pi_x <= p_x;
        pi_y <= p_y;
        pi_z <= p_z;
      end if;
    end if;
  end process;

  -- j index : counts the read request of the computing point, P[j]
  process (clk) 
  begin
    if rising_edge(clk) then
      if fsm_state = FSM_SETUP then
        j <= (others => '0');
      elsif j_req = '1' then
        j <= j + 1;
      end if;
    end if;
  end process;

  -- P[j]
  pj_x <= p_x;
  pj_y <= p_y;
  pj_z <= p_z;
  pj_m <= p_m;

  -- P[j] counter : counts the valid P[j] read from memory
  U_pjcnt: process (clk)
  begin
    if rising_edge(clk) then
      if fsm_state = FSM_SETUP then
        pj_cnt <= (others => '0');
      elsif vld = '1' then
        pj_cnt <= pj_cnt + 1;
      end if;
    end if;
  end process;

  -- FSM counter : count the clock cycles in drain and accumulate states
  U_cnt: process (clk)
  begin
    if rising_edge(clk) then
      if fsm_state = FSM_FEED then
        fsm_cnt <= (others => '0');
      else
        fsm_cnt <= fsm_cnt + 1;
      end if;
    end if;
  end process;

  conflict <= '1' when i = pj_cnt else '0'; -- avoid computing itself

  -- FSM control flow
  U_fsm: process (clk)
  begin
    if rising_edge(clk) then
      if rst = '1' then
        i_req_raw <= '0';
        j_req_raw <= '0';
        fsm_state <= FSM_IDLE;
      elsif en = '1' then
        case fsm_state is

          when FSM_IDLE =>                -- idle wait
            if start = '1' then           -- until external start
              i_req_raw <= '1';           -- request P[i]
              fsm_state <= FSM_SETUP;
            end if;

          when FSM_SETUP =>               -- setup phase
            if rdy = '1' then             -- after read command is issued
              i_req_raw <= '0';           -- don not request P[i] anymore
            end if;
            if vld = '1' then             -- after valid data of P[i]
              j_req_raw <= '1';           -- instead, request P[j]
              fsm_state <= FSM_FEED;
            end if;

          when FSM_FEED =>                -- computation phase
            if j = n and j_req = '1' then -- until all P[j] is read
              j_req_raw <= '0';           -- stop requesting P[j]
            end if;
            if pj_cnt = n and vld = '1' then  -- until all P[j] are processed
              fsm_state <= FSM_DRAIN;
            end if;

          when FSM_DRAIN =>               -- drain the pipeline
            if fsm_cnt = DRAIN_LENGTH then
              fsm_state <= FSM_ACCUMULATE;
            end if;

          when FSM_ACCUMULATE =>          -- drain the accumulator
            if fsm_cnt = ACC_LENGTH then
              fsm_state <= FSM_FINISH;
            end if;

          when FSM_FINISH =>
            if full = '0' then
              if i >= i_end then           -- if complete all P[i]
                fsm_state <= FSM_IDLE;    -- then wait for next start
              else
                i_req_raw <= '1';         -- else process next P[i]
                fsm_state <= FSM_SETUP;
              end if;
            end if;

        end case;
      end if;
    end if;
  end process;

  -- ACC input select
  -- Zeros the input to accumulator when conflicting i and j. The
  -- control bit pipes along side the data in data path. Thus avoiding
  -- a global enable signal to the data path.
  ctrl_vld <= vld and not conflict when fsm_state = FSM_FEED else '0';
  U_pipe: process(clk)
  begin
    if rising_edge(clk) then
      if rst = '1' then
        ctrl_pipe <= (others => '0');
      else
        ctrl_pipe <= ctrl_vld & ctrl_pipe(131 downto 1);
      end if;
    end if;
  end process;
  accs(0) <= ctrl_pipe(0);
  accs(1) <= '1' when fsm_state = FSM_ACCUMULATE else '0';
  accs(2) <= or_reduce(j(31 downto 4));

  -- ACC register enable
  U_acc_cnt: process (clk)
  begin
    if rising_edge(clk) then
      if rst = '1' or acc_cnt = X"B" or fsm_state = FSM_FINISH then
        acc_cnt <= "0000";
      elsif fsm_state = FSM_ACCUMULATE then
        acc_cnt <= acc_cnt + 1;
      end if;
    end if;
  end process;
  U_acc_ctrl: process (clk)
  begin
    if rising_edge(clk) then
      if rst = '1' or fsm_state = FSM_FEED then
        acc_run <= "000";
      elsif acc_cnt = X"B" then
        acc_run <= acc_run + 1;
      end if;
    end if;
  end process;
  accr <= '1' when (acc_run = "000" and acc_cnt(0) = '0') or
                   (acc_run = "001" and acc_cnt(1 downto 0) = "01") or
                   (acc_run = "010" and acc_cnt(2 downto 0) = "011") or
                   (acc_run = "011" and acc_cnt(3 downto 0) = "0111") or
                   (acc_run = "100" and acc_cnt(3 downto 0) = "0111") else
          '0';

  -- Debug Signals
  debug <= (others => '0');
--  debug(7 downto 0) <= i(7 downto 0);
--  debug(15 downto 8) <= j(7 downto 0);
--  debug(16) <= i_req_raw;
--  debug(17) <= j_req_raw;
--  debug(18) <= '1' when fsm_state = FSM_IDLE else '0';
--  debug(19) <= '1' when fsm_state = FSM_SETUP else '0';
--  debug(20) <= '1' when fsm_state = FSM_FEED else '0';
--  debug(21) <= '1' when fsm_state = FSM_DRAIN else '0';
--  debug(22) <= '1' when fsm_state = FSM_ACCUMULATE else '0';
--  debug(23) <= '1' when fsm_state = FSM_FINISH else '0';
--  debug(31 downto 24) <= fsm_cnt(7 downto 0);

END structural;
